﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CodeDom;
using System.Collections;
using System.Globalization;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace System.ComponentModel.Design.Serialization;

/// <summary>
///  This base class is used as a shared base between CodeDomSerializer and TypeCodeDomSerializer.
///  It is not meant to be publicly derived from.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract partial class CodeDomSerializerBase
{
    private static readonly Attribute[] s_runTimeProperties = [DesignOnlyAttribute.No];

    /// <summary>
    ///  Internal constructor so only we can derive from this class.
    /// </summary>
    internal CodeDomSerializerBase()
    {
    }

    /// <summary>
    ///  This method is invoked during deserialization to obtain an instance of an object. When this is called, an instance
    ///  of the requested type should be returned. The default implementation invokes manager.CreateInstance.
    /// </summary>
    protected virtual object DeserializeInstance(IDesignerSerializationManager manager, Type type, object?[]? parameters, string? name, bool addToContainer)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(type);

        return manager.CreateInstance(type, parameters, name, addToContainer);
    }

    /// <summary>
    ///  This routine returns the correct typename given a CodeTypeReference. It expands the child typenames
    ///  and builds up the clr formatted generic name. If its not a generic, it just returns BaseType.
    /// </summary>
    internal static string GetTypeNameFromCodeTypeReference(IDesignerSerializationManager manager, CodeTypeReference typeref)
    {
        // we do this to avoid an extra gettype for the usual nongeneric case.
        if (typeref.TypeArguments is null || typeref.TypeArguments.Count == 0)
        {
            return typeref.BaseType;
        }

        StringBuilder typeName = new();
        GetTypeNameFromCodeTypeReferenceHelper(manager, typeref, typeName);
        return typeName.ToString();
    }

    private static void GetTypeNameFromCodeTypeReferenceHelper(IDesignerSerializationManager manager, CodeTypeReference typeref, StringBuilder typeName)
    {
        if (typeref.TypeArguments is null || typeref.TypeArguments.Count == 0)
        {
            Type? t = manager.GetType(typeref.BaseType);
            // we use the assemblyqualifiedname where we can so that GetType will find it correctly.
            if (t is not null)
            {
                // get type which exists in the target framework if any
                typeName.Append(GetReflectionTypeFromTypeHelper(manager, t).AssemblyQualifiedName);
            }
            else
            {
                typeName.Append(typeref.BaseType);
            }
        }
        else
        {
            // create the MyGeneric`2[ part
            if (!typeref.BaseType.Contains('`'))
            {
                typeName.Append($"`{typeref.TypeArguments.Count}");
            }

            typeName.Append('[');

            // now create each sub-argument part.
            foreach (CodeTypeReference childref in typeref.TypeArguments)
            {
                typeName.Append('[');
                GetTypeNameFromCodeTypeReferenceHelper(manager, childref, typeName);
                typeName.Append("],");
            }

            typeName[^1] = ']';
        }
    }

    /// <summary>
    ///  Return a target framework-aware TypeDescriptionProvider which can be used for type filtering
    /// </summary>
    protected static TypeDescriptionProvider? GetTargetFrameworkProvider(IServiceProvider provider, object instance)
    {
        // service will be null outside the VisualStudio
        if (provider.TryGetService(out TypeDescriptionProviderService? service))
        {
            return service.GetProvider(instance);
        }

        return null;
    }

    private static bool TryGetTargetFrameworkProviderAndCheckType(IDesignerSerializationManager manager, object instance, [NotNullWhen(true)] out TypeDescriptionProvider? targetProvider)
    {
        Type type = instance.GetType();
        if (!type.IsValueType)
        {
            targetProvider = null;
            return false;
        }

        targetProvider = GetTargetFrameworkProvider(manager, instance);

        if (targetProvider is null)
        {
            return false;
        }

        if (targetProvider.IsSupportedType(type))
        {
            return true;
        }

        Error(manager, string.Format(SR.TypeNotFoundInTargetFramework, instance.GetType().FullName), SR.SerializerUndeclaredName);
        targetProvider = null;
        return false;
    }

    /// <summary>
    ///  Get a faux type which is generated from the metadata, which is
    ///  looked up on the target framework assembly. Be careful to not use mix
    ///  this type with runtime types in comparisons!
    /// </summary>
    protected static Type GetReflectionTypeFromTypeHelper(IDesignerSerializationManager manager, Type type)
    {
        if (type is null || manager is null)
        {
            Debug.Fail("GetReflectionTypeFromTypeHelper does not accept null arguments.");
            return null;
        }

        if (TryGetTargetFrameworkProviderForType(manager, type, out TypeDescriptionProvider? targetProvider))
        {
            if (targetProvider.IsSupportedType(type))
            {
                return targetProvider.GetReflectionType(type);
            }

            Error(manager, string.Format(SR.TypeNotFoundInTargetFramework, type.FullName), SR.SerializerUndeclaredName);
        }

        return TypeDescriptor.GetReflectionType(type);
    }

    [DoesNotReturn]
    internal static void Error(IDesignerSerializationManager manager, string exceptionText, string? helpLink)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(exceptionText);

        CodeStatement? statement = manager.GetContext<CodeStatement>();
        CodeLinePragma? linePragma = statement?.LinePragma;

        Exception exception = new CodeDomSerializerException(exceptionText, linePragma)
        {
            HelpLink = helpLink
        };
        throw exception;
    }

    private static bool TryGetTargetFrameworkProviderForType(IServiceProvider provider, Type type, [NotNullWhen(true)] out TypeDescriptionProvider? targetProvider)
    {
        // service will be null outside the VisualStudio
        targetProvider = provider.GetService<TypeDescriptionProviderService>()?.GetProvider(type);
        return targetProvider is not null;
    }

    /// <summary>
    ///  Get a faux type which is generated based on the metadata
    ///  looked up on the target framework assembly.
    ///  Never pass a type returned from GetReflectionType to runtime APIs that need a type.
    ///  Call GetRuntimeType first to unwrap the reflection type.
    /// </summary>
    protected static Type GetReflectionTypeHelper(IDesignerSerializationManager manager, object instance)
    {
        if (instance is null || manager is null)
        {
            Debug.Fail("GetReflectionTypeHelper does not accept null arguments.");
            return null;
        }

        if (TryGetTargetFrameworkProviderAndCheckType(manager, instance, out TypeDescriptionProvider? targetProvider))
        {
            return targetProvider.GetReflectionType(instance);
        }

        return TypeDescriptor.GetReflectionType(instance);
    }

    /// <summary>
    ///  Get properties collection as defined in the project target framework
    /// </summary>
    protected static PropertyDescriptorCollection GetPropertiesHelper(IDesignerSerializationManager manager, object instance, Attribute[]? attributes)
    {
        if (instance is null || manager is null)
        {
            Debug.Fail("GetPropertiesHelper does not accept null arguments.");
            return null;
        }

        if (TryGetTargetFrameworkProviderAndCheckType(manager, instance, out TypeDescriptionProvider? targetProvider))
        {
            if (targetProvider.GetTypeDescriptor(instance) is { } targetAwareDescriptor)
            {
                if (attributes is null)
                {
                    return targetAwareDescriptor.GetProperties();
                }

                return targetAwareDescriptor.GetProperties(attributes);
            }
        }

        if (attributes is null)
        {
            return TypeDescriptor.GetProperties(instance);
        }

        return TypeDescriptor.GetProperties(instance, attributes);
    }

    /// <summary>
    ///  Get events collection as defined in the project target framework
    /// </summary>
    protected static EventDescriptorCollection GetEventsHelper(IDesignerSerializationManager manager, object instance, Attribute[]? attributes)
    {
        if (instance is null || manager is null)
        {
            Debug.Fail("GetEventsHelper does not accept null arguments.");
            return null;
        }

        if (TryGetTargetFrameworkProviderAndCheckType(manager, instance, out TypeDescriptionProvider? targetProvider))
        {
            if (targetProvider.GetTypeDescriptor(instance) is { } targetAwareDescriptor)
            {
                if (attributes is null)
                {
                    return targetAwareDescriptor.GetEvents();
                }

                return targetAwareDescriptor.GetEvents(attributes);
            }
        }

        if (attributes is null)
        {
            return TypeDescriptor.GetEvents(instance);
        }

        return TypeDescriptor.GetEvents(instance, attributes);
    }

    /// <summary>
    ///  Get attributes collection as defined in the project target framework
    /// </summary>
    protected static AttributeCollection GetAttributesHelper(IDesignerSerializationManager manager, object instance)
    {
        if (instance is null || manager is null)
        {
            Debug.Fail("GetAttributesHelper does not accept null arguments.");
            return null;
        }

        if (TryGetTargetFrameworkProviderAndCheckType(manager, instance, out TypeDescriptionProvider? targetProvider))
        {
            if (targetProvider.GetTypeDescriptor(instance) is { } targetAwareDescriptor)
            {
                return targetAwareDescriptor.GetAttributes();
            }
        }

        return TypeDescriptor.GetAttributes(instance);
    }

    /// <summary>
    ///  Get attributes collection as defined in the project target framework
    /// </summary>
    protected static AttributeCollection GetAttributesFromTypeHelper(IDesignerSerializationManager manager, Type type)
    {
        if (type is null || manager is null)
        {
            Debug.Fail("GetAttributesFromTypeHelper does not accept null arguments.");
            return null;
        }

        if (type.IsValueType)
        {
            if (TryGetTargetFrameworkProviderForType(manager, type, out TypeDescriptionProvider? targetProvider))
            {
                if (targetProvider.IsSupportedType(type))
                {
                    if (targetProvider.GetTypeDescriptor(type) is { } targetAwareDescriptor)
                    {
                        return targetAwareDescriptor.GetAttributes();
                    }
                }
                else
                {
                    Error(manager, string.Format(SR.TypeNotFoundInTargetFramework, type.FullName), SR.SerializerUndeclaredName);
                }
            }
        }

        return TypeDescriptor.GetAttributes(type);
    }

    /// <summary>
    ///  This method will inspect all of the properties on the given object fitting the filter, and check for that
    ///  property in a resource blob. This is useful for deserializing properties that cannot be represented
    ///  in code, such as design-time properties.
    /// </summary>
    protected void DeserializePropertiesFromResources(IDesignerSerializationManager manager, object value, Attribute[]? filter)
    {
        // It is much faster to dig through the resources first, and then map these resources to properties than it is
        // to filter properties at each turn. Why?  Because filtering properties requires a separate filter call for
        // each object (because designers get a chance to filter, the cache is per-component), while resources are loaded
        // once per document.
        IDictionaryEnumerator? de = ResourceCodeDomSerializer.GetMetadataEnumerator(manager);
        de ??= ResourceCodeDomSerializer.GetEnumerator(manager, CultureInfo.InvariantCulture);

        if (de is not null)
        {
            string? ourObjectName;
            if (manager.TryGetContext(out RootContext? root) && root.Value == value)
            {
                ourObjectName = "$this";
            }
            else
            {
                ourObjectName = manager.GetName(value);
            }

            if (ourObjectName is null)
            {
                return;
            }

            PropertyDescriptorCollection ourProperties = GetPropertiesHelper(manager, value, null);
            while (de.MoveNext())
            {
                string resourceName = (de.Key as string)!;
                Debug.Assert(resourceName is not null, "non-string keys in dictionary entry");
                int dotIndex = resourceName.IndexOf('.');
                if (dotIndex == -1)
                {
                    continue;
                }

                // Skip now if this isn't a value for our object.
                if (!resourceName.AsSpan(0, dotIndex).Equals(ourObjectName, StringComparison.Ordinal))
                {
                    continue;
                }

                string propertyName = resourceName[(dotIndex + 1)..];

                // Now locate the property by this name.
                PropertyDescriptor? property = ourProperties[propertyName];
                if (property is null)
                {
                    continue;
                }

                // This property must have matching attributes.
                if (property.Attributes.Contains(filter))
                {
                    object? resourceObject = de.Value;
                    try
                    {
                        property.SetValue(value, resourceObject);
                    }
                    catch (Exception e)
                    {
                        manager.ReportError(e);
                    }
                }
            }
        }
    }

    /// <summary>
    ///  This is a helper method that will deserialize a given statement. It deserializes
    ///  the statement by interpreting and executing the CodeDom statement.
    /// </summary>
    protected void DeserializeStatement(IDesignerSerializationManager manager, CodeStatement statement)
    {
        // Push this statement onto the context stack.
        // This allows any serializers handling an expression to know what it was connected to.
        manager.Context.Push(statement);
        try
        {
            // Perf: change ordering based on possibility of occurrence
            if (statement is CodeAssignStatement cas)
            {
                DeserializeAssignStatement(manager, cas);
            }
            else if (statement is CodeVariableDeclarationStatement cvds)
            {
                DeserializeVariableDeclarationStatement(manager, cvds);
            }
            else if (statement is CodeCommentStatement)
            {
                // do nothing for comments. This just suppresses the debug warning
            }
            else if (statement is CodeExpressionStatement ces)
            {
                DeserializeExpression(manager, null, ces.Expression);
            }
            else if (statement is CodeMethodReturnStatement cmrs)
            {
                DeserializeExpression(manager, null, cmrs.Expression);
            }
            else if (statement is CodeAttachEventStatement caes)
            {
                DeserializeAttachEventStatement(manager, caes);
            }
            else if (statement is CodeRemoveEventStatement cres)
            {
                DeserializeDetachEventStatement(manager, cres);
            }
            else if (statement is CodeLabeledStatement cls)
            {
                DeserializeStatement(manager, cls.Statement);
            }
        }
        catch (CheckoutException)
        {
            throw; // we want to propagate those all the way up
        }
        catch (Exception e)
        {
            // Since we always go through reflection, don't show what our engine does, show what caused the problem.
            if (e is TargetInvocationException)
            {
                e = e.InnerException!;
            }

            if (e is not CodeDomSerializerException && statement.LinePragma is not null)
            {
                e = new CodeDomSerializerException(e, statement.LinePragma);
            }

            manager.ReportError(e);
        }
        finally
        {
            Debug.Assert(manager.Context.Current == statement, "Someone corrupted the context stack");
            manager.Context.Pop();
        }
    }

    private void DeserializeVariableDeclarationStatement(IDesignerSerializationManager manager, CodeVariableDeclarationStatement statement)
    {
        if (statement.InitExpression is not null)
        {
            DeserializeExpression(manager, statement.Name, statement.InitExpression);
        }
    }

    private void DeserializeDetachEventStatement(IDesignerSerializationManager manager, CodeRemoveEventStatement statement)
    {
        object? eventListener = DeserializeExpression(manager, null, statement.Listener);
        if (eventListener is CodeDelegateCreateExpression delegateCreate)
        {
            // We only support binding methods to the root object.
            object? eventAttachObject = DeserializeExpression(manager, null, delegateCreate.TargetObject);
            RootContext? rootExp = manager.GetContext<RootContext>();
            bool isRoot = rootExp is null || rootExp.Value == eventAttachObject;

            if (isRoot)
            {
                // Now deserialize the LHS of the event attach to discover the guy whose event we are attaching.
                object targetObject = DeserializeExpression(manager, null, statement.Event.TargetObject)!;

                if (targetObject is not CodeExpression)
                {
                    EventDescriptor? evt = GetEventsHelper(manager, targetObject, null)[statement.Event.EventName];
                    if (evt is not null)
                    {
                        IEventBindingService? evtSvc = manager.GetService<IEventBindingService>();
                        if (evtSvc is not null)
                        {
                            PropertyDescriptor prop = evtSvc.GetEventProperty(evt);
                            prop.SetValue(targetObject, null);
                        }
                    }
                    else
                    {
                        Error(manager, string.Format(SR.SerializerNoSuchEvent, targetObject.GetType().FullName, statement.Event.EventName), SR.SerializerNoSuchEvent);
                    }
                }
            }
        }
    }

    private void DeserializeAssignStatement(IDesignerSerializationManager manager, CodeAssignStatement statement)
    {
        // Since we're doing an assignment into something, we need to know what that something is.
        // It can be a property, a variable, or a member. Anything else is invalid.
        // Perf: is -> as changes, change ordering based on possibility of occurrence
        CodeExpression expression = statement.Left;

        if (expression is CodePropertyReferenceExpression propertyReferenceEx)
        {
            DeserializePropertyAssignStatement(manager, statement, propertyReferenceEx, true);
        }
        else if (expression is CodeFieldReferenceExpression fieldReferenceEx)
        {
            object? lhs = DeserializeExpression(manager, fieldReferenceEx.FieldName, fieldReferenceEx.TargetObject);
            if (lhs is not null)
            {
                if (manager.TryGetContext(out RootContext? root) && root.Value == lhs)
                {
                    object? rhs = DeserializeExpression(manager, fieldReferenceEx.FieldName, statement.Right);
                    if (rhs is CodeExpression)
                    {
                        return;
                    }
                }
                else
                {
                    FieldInfo? f;
                    object? instance;

                    if (lhs is Type type)
                    {
                        instance = null;
                        f = GetReflectionTypeFromTypeHelper(manager, type).GetField(fieldReferenceEx.FieldName, BindingFlags.GetField | BindingFlags.Static | BindingFlags.Public);
                    }
                    else
                    {
                        instance = lhs;
                        f = GetReflectionTypeHelper(manager, lhs).GetField(fieldReferenceEx.FieldName, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public);
                    }

                    if (f is not null)
                    {
                        object? rhs = DeserializeExpression(manager, fieldReferenceEx.FieldName, statement.Right);
                        if (rhs is CodeExpression)
                        {
                            return;
                        }

                        if (rhs is IConvertible ic)
                        {
                            // f.FieldType is a type from the reflection (or project target) universe,
                            // while rhs is a runtime type (exists in the Visual Studio framework)
                            // they need to be converted to the same universe for comparison to work.
                            // If TargetFrameworkProvider is not available, then we are working with runtime types.
                            Type fieldType = f.FieldType;
                            if (TryGetTargetFrameworkProviderForType(manager, fieldType, out TypeDescriptionProvider? tdp))
                            {
                                fieldType = tdp.GetRuntimeType(fieldType);
                            }

                            if (fieldType != rhs.GetType())
                            {
                                try
                                {
                                    rhs = ic.ToType(fieldType, null);
                                }
                                catch
                                {
                                    // oh well...
                                }
                            }
                        }

                        f.SetValue(instance, rhs);
                    }
                    else
                    {
                        // lets try it as a property:
                        CodePropertyReferenceExpression propRef = new CodePropertyReferenceExpression
                        {
                            TargetObject = fieldReferenceEx.TargetObject,
                            PropertyName = fieldReferenceEx.FieldName
                        };

                        if (!DeserializePropertyAssignStatement(manager, statement, propRef, false))
                        {
                            Error(manager, string.Format(SR.SerializerNoSuchField, lhs.GetType().FullName, fieldReferenceEx.FieldName), SR.SerializerNoSuchField);
                        }
                    }
                }
            }
        }
        else if (expression is CodeVariableReferenceExpression variableReferenceEx)
        {
            // This is the easiest. Just relate the RHS object to the name of the variable.
            object rhs = DeserializeExpression(manager, variableReferenceEx.VariableName, statement.Right)!;
            if (rhs is CodeExpression)
            {
                return;
            }

            manager.SetName(rhs, variableReferenceEx.VariableName);
        }
        else if (expression is CodeArrayIndexerExpression arrayIndexerEx)
        {
            int[] indexes = new int[arrayIndexerEx.Indices.Count];
            object? array = DeserializeExpression(manager, null, arrayIndexerEx.TargetObject);

            // The indexes have to be of type int32. If they're not, then we cannot assign to this array.
            for (int i = 0; i < indexes.Length; i++)
            {
                object? index = DeserializeExpression(manager, null, arrayIndexerEx.Indices[i]);
                if (index is IConvertible ic)
                {
                    indexes[i] = ic.ToInt32(null);
                }
                else
                {
                    return;
                }
            }

            if (array is Array arr)
            {
                object? rhs = DeserializeExpression(manager, null, statement.Right);
                if (rhs is CodeExpression)
                {
                    return;
                }

                arr.SetValue(rhs, indexes);
            }
        }
    }

    /// <summary>
    ///  This is a helper method that will deserialize a given expression. It deserializes
    ///  the statement by interpreting and executing the CodeDom expression, returning
    ///  the results.
    /// </summary>
    protected object? DeserializeExpression(IDesignerSerializationManager manager, string? name, CodeExpression? expression)
    {
        object? result = expression;

        // Perf: is -> as changes, change ordering based on possibility of occurrence
        // If you are adding to this, use as instead of is + cast and order new expressions in order of frequency in typical user code.

        while (result is not null)
        {
            if (result is CodePrimitiveExpression primitiveEx)
            {
                result = primitiveEx.Value;
                break;
            }
            else if (result is CodePropertyReferenceExpression propertyReferenceEx)
            {
                result = DeserializePropertyReferenceExpression(manager, propertyReferenceEx, true);
                break;
            }
            else if (result is CodeThisReferenceExpression)
            {
                // (is -> as doesn't help here, since the cast is different)
                if (manager.TryGetContext(out RootContext? rootExp))
                {
                    result = rootExp.Value;
                }
                else
                {
                    // Last ditch effort. Some things have to code gen against "this", such as event wireups.
                    // Those are always bound against the root component.
                    if (manager.GetService<IDesignerHost>() is { } host)
                    {
                        result = host.RootComponent;
                    }
                }

                if (result is null)
                {
                    Error(manager, SR.SerializerNoRootExpression, SR.SerializerNoRootExpression);
                }

                break;
            }
            else if (result is CodeTypeReferenceExpression typeReferenceEx)
            {
                result = manager.GetType(GetTypeNameFromCodeTypeReference(manager, typeReferenceEx.Type));
                break;
            }
            else if (result is CodeObjectCreateExpression objectCreateEx)
            {
                result = null;
                Type? type = manager.GetType(GetTypeNameFromCodeTypeReference(manager, objectCreateEx.CreateType));
                if (type is not null)
                {
                    object?[] parameters = new object[objectCreateEx.Parameters.Count];
                    bool paramsOk = true;
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        parameters[i] = DeserializeExpression(manager, null, objectCreateEx.Parameters[i]);
                        if (parameters[i] is CodeExpression)
                        {
                            // Before we bail on this parameter, see if the type is a delegate.
                            // If we are creating a delegate we may be able to bind to the method after all.
                            if (typeof(Delegate).IsAssignableFrom(type) && parameters is [CodeMethodReferenceExpression methodRef])
                            {
                                // Only do this if our target is not the root context.
                                if (methodRef.TargetObject is not CodeThisReferenceExpression)
                                {
                                    object target = DeserializeExpression(manager, null, methodRef.TargetObject)!;
                                    if (target is not CodeExpression)
                                    {
                                        // Search for a matching method sig. Must be public since we don't own this object
                                        MethodInfo? delegateInvoke = type.GetMethod("Invoke");
                                        if (delegateInvoke is not null)
                                        {
                                            ParameterInfo[] delegateParams = delegateInvoke.GetParameters();
                                            Type[] paramTypes = new Type[delegateParams.Length];
                                            for (int idx = 0; idx < paramTypes.Length; idx++)
                                            {
                                                paramTypes[idx] = delegateParams[i].ParameterType;
                                            }

                                            MethodInfo? mi = GetReflectionTypeHelper(manager, target).GetMethod(methodRef.MethodName, paramTypes);
                                            if (mi is not null)
                                            {
                                                // MethodInfo from the reflection Universe might
                                                // not implement MethodHandle property,
                                                // once we know that the method is available,
                                                // get it from the runtime type.
                                                mi = target.GetType().GetMethod(methodRef.MethodName, paramTypes)!;
                                                result = Activator.CreateInstance(type, [target, mi.MethodHandle.GetFunctionPointer()]);
                                            }
                                        }
                                    }
                                }
                            }

                            // Technically, the parameters are not OK. Our special case above, if successful, would have produced a "result" object for us.
                            paramsOk = false;
                            break;
                        }
                    }

                    if (paramsOk)
                    {
                        // Create an instance of the object. If the caller provided a name, then ask the manager to add this object to the container.
                        result = DeserializeInstance(manager, type, parameters, name, (name is not null));
                    }
                }
                else
                {
                    Error(manager, string.Format(SR.SerializerTypeNotFound, objectCreateEx.CreateType.BaseType), SR.SerializerTypeNotFound);
                }

                break;
            }
            else if (result is CodeArgumentReferenceExpression argumentReferenceEx)
            {
                result = manager.GetInstance(argumentReferenceEx.ParameterName);
                if (result is null)
                {
                    Error(manager, string.Format(SR.SerializerUndeclaredName, argumentReferenceEx.ParameterName), SR.SerializerUndeclaredName);
                }

                break;
            }
            else if (result is CodeFieldReferenceExpression fieldReferenceEx)
            {
                object? target = DeserializeExpression(manager, null, fieldReferenceEx.TargetObject);
                if (target is not null and not CodeExpression)
                {
                    // If the target is the root object, then this won't be found through reflection. Instead, ask the manager for the field by name.
                    RootContext? rootExp = manager.GetContext<RootContext>();
                    if (rootExp is not null && rootExp.Value == target)
                    {
                        object? namedObject = manager.GetInstance(fieldReferenceEx.FieldName);
                        if (namedObject is not null)
                        {
                            result = namedObject;
                        }
                        else
                        {
                            Error(manager, string.Format(SR.SerializerUndeclaredName, fieldReferenceEx.FieldName), SR.SerializerUndeclaredName);
                        }
                    }
                    else
                    {
                        FieldInfo? field;
                        object? instance;
                        if (target is Type t)
                        {
                            instance = null;
                            field = GetReflectionTypeFromTypeHelper(manager, t).GetField(fieldReferenceEx.FieldName, BindingFlags.GetField | BindingFlags.Static | BindingFlags.Public);
                        }
                        else
                        {
                            instance = target;
                            field = GetReflectionTypeHelper(manager, target).GetField(fieldReferenceEx.FieldName, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public);
                        }

                        if (field is not null)
                        {
                            result = field.GetValue(instance);
                        }
                        else
                        {
                            // lets try it as a property:
                            CodePropertyReferenceExpression propRef = new CodePropertyReferenceExpression
                            {
                                TargetObject = fieldReferenceEx.TargetObject,
                                PropertyName = fieldReferenceEx.FieldName
                            };

                            result = DeserializePropertyReferenceExpression(manager, propRef, false);
                            if (result == fieldReferenceEx)
                            {
                                Error(manager, string.Format(SR.SerializerUndeclaredName, fieldReferenceEx.FieldName), SR.SerializerUndeclaredName);
                            }
                        }
                    }
                }
                else
                {
                    Error(manager, string.Format(SR.SerializerFieldTargetEvalFailed, fieldReferenceEx.FieldName), SR.SerializerFieldTargetEvalFailed);
                }

                break;
            }
            else if (result is CodeMethodInvokeExpression methodInvokeEx)
            {
                object? targetObject = DeserializeExpression(manager, null, methodInvokeEx.Method.TargetObject);

                if (targetObject is not null)
                {
                    object?[] parameters = new object[methodInvokeEx.Parameters.Count];
                    bool paramsOk = true;

                    for (int i = 0; i < parameters.Length; i++)
                    {
                        parameters[i] = DeserializeExpression(manager, null, methodInvokeEx.Parameters[i]);
                        if (parameters[i] is CodeExpression)
                        {
                            paramsOk = false;
                            break;
                        }
                    }

                    if (paramsOk)
                    {
                        var changeService = manager.GetService<IComponentChangeService>();

                        if (targetObject is Type t)
                        {
                            result = GetReflectionTypeFromTypeHelper(manager, t).InvokeMember(methodInvokeEx.Method.MethodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, parameters, null, null, null);
                        }
                        else
                        {
                            changeService?.OnComponentChanging(targetObject, null);

                            try
                            {
                                result = GetReflectionTypeHelper(manager, targetObject).InvokeMember(methodInvokeEx.Method.MethodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, targetObject, parameters, null, null, null);
                            }
                            catch (MissingMethodException)
                            {
                                // We did not find the method directly. Let's see if we can find it
                                // as an private implemented interface name.

                                if (methodInvokeEx.Method.TargetObject is CodeCastExpression castExpr)
                                {
                                    Type? castType = manager.GetType(GetTypeNameFromCodeTypeReference(manager, castExpr.TargetType));

                                    if (castType is not null && castType.IsInterface)
                                    {
                                        result = GetReflectionTypeFromTypeHelper(manager, castType).InvokeMember(methodInvokeEx.Method.MethodName, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, targetObject, parameters, null, null, null);
                                    }
                                    else
                                    {
                                        throw;
                                    }
                                }
                                else
                                {
                                    throw;
                                }
                            }

                            changeService?.OnComponentChanged(targetObject);
                        }
                    }
                    else if (parameters is [CodeDelegateCreateExpression codeDelegateCreateExpression])
                    {
                        string methodName = methodInvokeEx.Method.MethodName;

                        if (methodName.StartsWith("add_", StringComparison.Ordinal))
                        {
                            methodName = methodName[4..];
                            DeserializeAttachEventStatement(manager, new CodeAttachEventStatement(methodInvokeEx.Method.TargetObject, methodName, codeDelegateCreateExpression));
                            result = null;
                        }
                    }
                }

                break;
            }
            else if (result is CodeVariableReferenceExpression variableReferenceEx)
            {
                result = manager.GetInstance(variableReferenceEx.VariableName);
                if (result is null)
                {
                    Error(manager, string.Format(SR.SerializerUndeclaredName, variableReferenceEx.VariableName), SR.SerializerUndeclaredName);
                }

                break;
            }
            else if (result is CodeCastExpression castEx)
            {
                result = DeserializeExpression(manager, name, castEx.Expression);
                if (result is IConvertible ic)
                {
                    Type? targetType = manager.GetType(GetTypeNameFromCodeTypeReference(manager, castEx.TargetType));
                    if (targetType is not null)
                    {
                        result = ic.ToType(targetType, null);
                    }
                }

                break;
            }
            else if (result is CodeBaseReferenceExpression)
            {
                // (is -> as doesn't help here, since the cast is different)
                RootContext? rootExp = manager.GetContext<RootContext>();
                result = rootExp?.Value;

                break;
            }
            else if (result is CodeArrayCreateExpression arrayCreateEx)
            {
                Type? arrayType = manager.GetType(GetTypeNameFromCodeTypeReference(manager, arrayCreateEx.CreateType));
                Array? array = null;

                if (arrayType is not null)
                {
                    if (arrayCreateEx.Initializers.Count > 0)
                    {
                        // Passed an array of initializers. Use this to create the array. Note that we use an ArrayList
                        // here and add elements as we create them. It is possible that an element cannot be resolved.
                        // This is an error, but we do not want to tank the entire array. If we kicked out the entire
                        // statement, a missing control would cause all controls on a form to vanish.
                        ArrayList arrayList = new(arrayCreateEx.Initializers.Count);

                        foreach (CodeExpression initializer in arrayCreateEx.Initializers)
                        {
                            try
                            {
                                object? o = DeserializeExpression(manager, null, initializer);

                                if (o is not CodeExpression)
                                {
                                    if (!arrayType.IsInstanceOfType(o))
                                    {
                                        o = Convert.ChangeType(o, arrayType, CultureInfo.InvariantCulture);
                                    }

                                    arrayList.Add(o);
                                }
                            }
                            catch (Exception ex)
                            {
                                manager.ReportError(ex);
                            }
                        }

                        array = Array.CreateInstance(arrayType, arrayList.Count);
                        arrayList.CopyTo(array, 0);
                    }
                    else if (arrayCreateEx.SizeExpression is not null)
                    {
                        object? o = DeserializeExpression(manager, name, arrayCreateEx.SizeExpression);
                        Debug.Assert(o is IConvertible, $"Array size expression could not be resolved to IConvertible: {(o.GetType().Name)}");

                        if (o is IConvertible ic)
                        {
                            int size = ic.ToInt32(null);
                            array = Array.CreateInstance(arrayType, size);
                        }
                    }
                    else
                    {
                        array = Array.CreateInstance(arrayType, arrayCreateEx.Size);
                    }
                }
                else
                {
                    Error(manager, string.Format(SR.SerializerTypeNotFound, arrayCreateEx.CreateType.BaseType), SR.SerializerTypeNotFound);
                }

                result = array;
                if (result is not null && name is not null)
                {
                    manager.SetName(result, name);
                }

                break;
            }
            else if (result is CodeArrayIndexerExpression arrayIndexerEx)
            {
                // For this, assume in any error we return a null. The only errors here should come from a mal-formed expression.
                result = null;

                if (DeserializeExpression(manager, name, arrayIndexerEx.TargetObject) is Array array)
                {
                    int[] indexes = new int[arrayIndexerEx.Indices.Count];

                    bool indexesOK = true;

                    // The indexes have to be of type int32. If they're not, then we cannot assign to this array.
                    for (int i = 0; i < indexes.Length; i++)
                    {
                        object? index = DeserializeExpression(manager, name, arrayIndexerEx.Indices[i]);

                        if (index is IConvertible convertible)
                        {
                            indexes[i] = convertible.ToInt32(null);
                        }
                        else
                        {
                            indexesOK = false;
                            break;
                        }
                    }

                    if (indexesOK)
                    {
                        result = array.GetValue(indexes);
                    }
                }

                break;
            }
            else if (result is CodeBinaryOperatorExpression binaryOperatorEx)
            {
                object? left = DeserializeExpression(manager, null, binaryOperatorEx.Left);
                object? right = DeserializeExpression(manager, null, binaryOperatorEx.Right);

                // We assign the result to an arbitrary value here in case the operation could not be performed.
                result = left;

                if (left is IConvertible icLeft && right is IConvertible icRight)
                {
                    result = ExecuteBinaryExpression(icLeft, icRight, binaryOperatorEx.Operator);
                }

                break;
            }
            else if (result is CodeDelegateInvokeExpression delegateInvokeEx)
            {
                object? targetObject = DeserializeExpression(manager, null, delegateInvokeEx.TargetObject);
                if (targetObject is Delegate del)
                {
                    object?[] parameters = new object[delegateInvokeEx.Parameters.Count];
                    bool paramsOk = true;
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        parameters[i] = DeserializeExpression(manager, null, delegateInvokeEx.Parameters[i]);
                        if (parameters[i] is CodeExpression)
                        {
                            paramsOk = false;
                            break;
                        }
                    }

                    if (paramsOk)
                    {
                        del.DynamicInvoke(parameters);
                    }
                }

                break;
            }
            else if (result is CodeDirectionExpression directionEx)
            {
                result = DeserializeExpression(manager, name, directionEx.Expression);
                break;
            }
            else if (result is CodeIndexerExpression indexerEx)
            {
                // For this, assume in any error we return a null. The only errors here should come from a mal-formed expression.
                result = null;
                object? targetObject = DeserializeExpression(manager, null, indexerEx.TargetObject);
                if (targetObject is not null)
                {
                    object?[] indexes = new object[indexerEx.Indices.Count];
                    bool indexesOK = true;

                    for (int i = 0; i < indexes.Length; i++)
                    {
                        indexes[i] = DeserializeExpression(manager, null, indexerEx.Indices[i]);
                        if (indexes[i] is CodeExpression)
                        {
                            indexesOK = false;
                            break;
                        }
                    }

                    if (indexesOK)
                    {
                        result = GetReflectionTypeHelper(manager, targetObject).InvokeMember("Item", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, targetObject, indexes, null, null, null);
                    }
                }

                break;
            }
            else if (result is CodeSnippetExpression)
            {
                result = null;
                break;
            }
            else if (result is CodeParameterDeclarationExpression parameterDeclaration)
            {
                result = manager.GetType(GetTypeNameFromCodeTypeReference(manager, parameterDeclaration.Type));
                break;
            }
            else if (result is CodeTypeOfExpression typeOfExpression)
            {
                string type = GetTypeNameFromCodeTypeReference(manager, typeOfExpression.Type);
                // add the array ranks so we get the right type of this thing.
                for (int i = 0; i < typeOfExpression.Type.ArrayRank; i++)
                {
                    type += "[]";
                }

                result = manager.GetType(type);
                if (result is null)
                {
                    Error(manager, string.Format(SR.SerializerTypeNotFound, type), SR.SerializerTypeNotFound);
                }

                break;
            }
            else if (result is CodeEventReferenceExpression or CodeMethodReferenceExpression or CodeDelegateCreateExpression)
            {
                // These are all the expressions we know about, but expect to return to the caller because we cannot simplify them.
                break;
            }
            else
            {
                // All expression evaluation happens above.
                // This codepath indicates that we got some sort of junk in the evaluator,
                // or that someone assigned result to a value without breaking out of the loop.
                Debug.Fail($"Unrecognized expression type: {result.GetType().Name}");
                break;
            }
        }

        return result;
    }

    private void DeserializeAttachEventStatement(IDesignerSerializationManager manager, CodeAttachEventStatement statement)
    {
        string? handlerMethodName = null;
        object? eventAttachObject = null;

        // Get the target information
        object? targetObject = DeserializeExpression(manager, null, statement.Event.TargetObject);
        string eventName = statement.Event.EventName;
        Debug.Assert(targetObject is not null, "Failed to get target object for event attach");
        Debug.Assert(eventName is not null, "Failed to get eventName for event attach");
        if (eventName is null || targetObject is null)
        {
            return;
        }

        if (statement.Listener is CodeObjectCreateExpression objCreate)
        {
            // now walk into the CodeObjectCreateExpression and get the parameters so we can get the name of the method, e.g. button1_Click
            if (objCreate.Parameters.Count == 1)
            {
                // if this is a delegate create (new EventHandler(this.button1_Click)), then the first parameter should be a method ref.
                if (objCreate.Parameters[0] is CodeMethodReferenceExpression methodRef)
                {
                    handlerMethodName = methodRef.MethodName;
                    eventAttachObject = DeserializeExpression(manager, null, methodRef.TargetObject);
                }
            }
            else
            {
                Debug.Fail("Encountered delegate object create with more or less than 1 parameter?");
            }
        }
        else
        {
            object? eventListener = DeserializeExpression(manager, null, statement.Listener);
            if (eventListener is CodeDelegateCreateExpression delegateCreate)
            {
                eventAttachObject = DeserializeExpression(manager, null, delegateCreate.TargetObject);
                handlerMethodName = delegateCreate.MethodName;
            }
        }

        RootContext? rootExp = manager.GetContext<RootContext>();
        bool isRoot = rootExp is null || rootExp.Value == eventAttachObject;

        if (handlerMethodName is null || !isRoot)
        {
            // We only support binding methods to the root object.
            return;
        }

        // Now deserialize the LHS of the event attach to discover the guy whose
        // event we are attaching.
        if (targetObject is not CodeExpression)
        {
            EventDescriptor? evt = GetEventsHelper(manager, targetObject, null)[eventName];

            if (evt is not null)
            {
                IEventBindingService? evtSvc = manager.GetService<IEventBindingService>();

                if (evtSvc is not null)
                {
                    PropertyDescriptor prop = evtSvc.GetEventProperty(evt);
                    prop.SetValue(targetObject, handlerMethodName);
                }
            }
            else
            {
                Error(manager, string.Format(SR.SerializerNoSuchEvent, targetObject.GetType().FullName, eventName), SR.SerializerNoSuchEvent);
            }
        }
    }

    private static object ExecuteBinaryExpression(IConvertible left, IConvertible right, CodeBinaryOperatorType op)
    {
        // "Binary" operator type is actually a combination of several types of operators:
        // boolean, binary and math. Group them into categories here.

        // Figure out what kind of expression we have.
        switch (op)
        {
            case CodeBinaryOperatorType.BitwiseOr:
            case CodeBinaryOperatorType.BitwiseAnd:
                return ExecuteBinaryOperator(left, right, op);

            case CodeBinaryOperatorType.Add:
            case CodeBinaryOperatorType.Subtract:
            case CodeBinaryOperatorType.Multiply:
            case CodeBinaryOperatorType.Divide:
            case CodeBinaryOperatorType.Modulus:
                return ExecuteMathOperator(left, right, op);

            case CodeBinaryOperatorType.IdentityInequality:
            case CodeBinaryOperatorType.IdentityEquality:
            case CodeBinaryOperatorType.ValueEquality:
            case CodeBinaryOperatorType.BooleanOr:
            case CodeBinaryOperatorType.BooleanAnd:
            case CodeBinaryOperatorType.LessThan:
            case CodeBinaryOperatorType.LessThanOrEqual:
            case CodeBinaryOperatorType.GreaterThan:
            case CodeBinaryOperatorType.GreaterThanOrEqual:
                return ExecuteBooleanOperator(left, right, op);

            default:
                Debug.Fail($"Unsupported binary operator type: {op}");
                return left;
        }
    }

    private static object ExecuteBinaryOperator(IConvertible left, IConvertible right, CodeBinaryOperatorType op)
    {
        TypeCode leftType = left.GetTypeCode();
        TypeCode rightType = right.GetTypeCode();

        // The compatible types are listed in order from lowest bitness to highest.
        // We must operate on the highest bitness to keep fidelity.
        ReadOnlySpan<TypeCode> compatibleTypes =
        [
            TypeCode.Byte,
            TypeCode.Char,
            TypeCode.Int16,
            TypeCode.UInt16,
            TypeCode.Int32,
            TypeCode.UInt32,
            TypeCode.Int64,
            TypeCode.UInt64
        ];

        int leftTypeIndex = -1;
        int rightTypeIndex = -1;

        for (int i = 0; i < compatibleTypes.Length; i++)
        {
            if (leftType == compatibleTypes[i])
            {
                leftTypeIndex = i;
            }

            if (rightType == compatibleTypes[i])
            {
                rightTypeIndex = i;
            }

            if (leftTypeIndex != -1 && rightTypeIndex != -1)
            {
                break;
            }
        }

        if (leftTypeIndex == -1 || rightTypeIndex == -1)
        {
            Debug.Fail("Could not convert left or right side to binary-compatible value.");
            return left;
        }

        int maxIndex = Math.Max(leftTypeIndex, rightTypeIndex);
        object result = left;
        switch (compatibleTypes[maxIndex])
        {
            case TypeCode.Byte:
                {
                    byte leftValue = left.ToByte(null);
                    byte rightValue = right.ToByte(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.Char:
                {
                    char leftValue = left.ToChar(null);
                    char rightValue = right.ToChar(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.Int16:
                {
                    short leftValue = left.ToInt16(null);
                    short rightValue = right.ToInt16(null);
                    if (op == CodeBinaryOperatorType.BitwiseOr)
                    {
                        result = (short)((ushort)leftValue | (ushort)rightValue);
                    }
                    else
                    {
                        result = leftValue & rightValue;
                    }

                    break;
                }

            case TypeCode.UInt16:
                {
                    ushort leftValue = left.ToUInt16(null);
                    ushort rightValue = right.ToUInt16(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.Int32:
                {
                    int leftValue = left.ToInt32(null);
                    int rightValue = right.ToInt32(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.UInt32:
                {
                    uint leftValue = left.ToUInt32(null);
                    uint rightValue = right.ToUInt32(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.Int64:
                {
                    long leftValue = left.ToInt64(null);
                    long rightValue = right.ToInt64(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }

            case TypeCode.UInt64:
                {
                    ulong leftValue = left.ToUInt64(null);
                    ulong rightValue = right.ToUInt64(null);
                    result = ExecuteBinaryOperator(leftValue, rightValue, op);
                    break;
                }
        }

        if (result != left && left is Enum)
        {
            // For enums, try to convert back to the original type
            result = Enum.ToObject(left.GetType(), result);
        }

        return result;

        static object ExecuteBinaryOperator<T>(T leftValue, T rightValue, CodeBinaryOperatorType op) where T : IBinaryInteger<T>
        {
            if (op == CodeBinaryOperatorType.BitwiseOr)
            {
                return leftValue | rightValue;
            }
            else
            {
                return leftValue & rightValue;
            }
        }
    }

    private static bool ExecuteBooleanOperator(IConvertible left, IConvertible right, CodeBinaryOperatorType op)
    {
        bool result = false;
        switch (op)
        {
            case CodeBinaryOperatorType.IdentityInequality:
                result = (left != right);
                break;
            case CodeBinaryOperatorType.IdentityEquality:
                result = (left == right);
                break;
            case CodeBinaryOperatorType.ValueEquality:
                result = left.Equals(right);
                break;
            case CodeBinaryOperatorType.BooleanOr:
                result = (left.ToBoolean(null) || right.ToBoolean(null));
                break;
            case CodeBinaryOperatorType.BooleanAnd:
                result = (left.ToBoolean(null) && right.ToBoolean(null));
                break;
            case CodeBinaryOperatorType.LessThan:
                // Not doing these at design time.
                break;
            case CodeBinaryOperatorType.LessThanOrEqual:
                // Not doing these at design time.
                break;
            case CodeBinaryOperatorType.GreaterThan:
                // Not doing these at design time.
                break;
            case CodeBinaryOperatorType.GreaterThanOrEqual:
                // Not doing these at design time.
                break;
            default:
                Debug.Fail("Should never get here!");
                break;
        }

        return result;
    }

    private static object ExecuteMathOperator(IConvertible left, IConvertible right, CodeBinaryOperatorType op)
    {
        if (op == CodeBinaryOperatorType.Add)
        {
            if (left is string or char && right is string or char)
            {
                return $"{left}{right}";
            }
            else
            {
                Debug.Fail("Addition operator not supported for this type");
            }
        }
        else
        {
            Debug.Fail("Math operators are not supported");
        }

        return left;
    }

    private object? DeserializePropertyReferenceExpression(IDesignerSerializationManager manager, CodePropertyReferenceExpression propertyReferenceEx, bool reportError)
    {
        object? result = propertyReferenceEx;
        object? target = DeserializeExpression(manager, null, propertyReferenceEx.TargetObject);

        if (target is not null and not CodeExpression)
        {
            Type? targetAsType = target as Type;
            // if it's a type, then we've got ourselves a static field...
            if (targetAsType is null)
            {
                PropertyDescriptor? prop = GetPropertiesHelper(manager, target, null)[propertyReferenceEx.PropertyName];
                if (prop is not null)
                {
                    result = prop.GetValue(target);
                }
                else
                {
                    // This could be a protected property on the base class.
                    // Make sure we're actually accessing through the base class,
                    // and then get the property directly from reflection.
                    if (GetExpression(manager, target) is CodeThisReferenceExpression)
                    {
                        PropertyInfo? propInfo = GetReflectionTypeHelper(manager, target).GetProperty(propertyReferenceEx.PropertyName, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                        if (propInfo is not null)
                        {
                            result = propInfo.GetValue(target, null);
                        }
                    }
                }
            }
            else
            {
                PropertyInfo? prop = GetReflectionTypeFromTypeHelper(manager, targetAsType).GetProperty(propertyReferenceEx.PropertyName, BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.Public);
                if (prop is not null)
                {
                    result = prop.GetValue(null, null);
                }
            }

            if (result == propertyReferenceEx && reportError)
            {
                string? typeName = targetAsType is not null ? targetAsType.FullName : GetReflectionTypeHelper(manager, target).FullName;
                Error(manager, string.Format(SR.SerializerNoSuchProperty, typeName, propertyReferenceEx.PropertyName), SR.SerializerNoSuchProperty);
            }
        }

        return result;
    }

    private bool DeserializePropertyAssignStatement(IDesignerSerializationManager manager, CodeAssignStatement statement,
        CodePropertyReferenceExpression propertyReferenceEx, bool reportError)
    {
        object? lhs = DeserializeExpression(manager, null, propertyReferenceEx.TargetObject);

        if (lhs is not null and not CodeExpression)
        {
            // Property assignments must go through our type descriptor system. However, we do not support parameterized
            // properties. If there are any parameters on the property, we do not perform the assignment.
            PropertyDescriptorCollection properties = GetPropertiesHelper(manager, lhs, s_runTimeProperties);
            PropertyDescriptor? p = properties[propertyReferenceEx.PropertyName];
            if (p is not null)
            {
                object? rhs = DeserializeExpression(manager, null, statement.Right);
                if (rhs is CodeExpression)
                {
                    return false;
                }

                if (rhs is IConvertible ic && p.PropertyType != rhs.GetType())
                {
                    try
                    {
                        rhs = ic.ToType(p.PropertyType, null);
                    }
                    catch
                    {
                    }
                }

                // We need to ensure that no virtual types leak into the runtime code
                // So if we ever assign a property value to a Type -- we make sure that the Type is a
                // real System.Type.
                if (rhs is Type rhsType && rhsType.UnderlyingSystemType is not null)
                {
                    rhs = rhsType.UnderlyingSystemType; // unwrap this "type" that came because it was not actually a real bcl type.
                }

                // Next: see if the RHS of this expression was a property reference too. If it was, then
                // we will look for a MemberRelationshipService to record the relationship between these
                // two properties, if supported.
                // We need to setup this MemberRelationship before we actually set the property value.
                // If we do it the other way around the new property value will be pushed into the old
                // relationship, which isn't a problem during normal serialization (since it not very
                // likely the property has already been assigned to), but it does affect undo.
                MemberRelationship oldRelation = MemberRelationship.Empty;
                if (manager.TryGetService(out MemberRelationshipService? relationships))
                {
                    if (statement.Right is CodePropertyReferenceExpression rhsPropRef)
                    {
                        object rhsPropTarget = DeserializeExpression(manager, null, rhsPropRef.TargetObject)!;
                        PropertyDescriptor? rhsProp = GetPropertiesHelper(manager, rhsPropTarget, null)[rhsPropRef.PropertyName];

                        if (rhsProp is not null)
                        {
                            MemberRelationship source = new(lhs, p);
                            MemberRelationship target = new(rhsPropTarget, rhsProp);

                            oldRelation = relationships[source];

                            if (relationships.SupportsRelationship(source, target))
                            {
                                relationships[source] = target;
                            }
                        }
                    }
                    else
                    {
                        oldRelation = relationships[lhs, p];
                        relationships[lhs, p] = MemberRelationship.Empty;
                    }
                }

                try
                {
                    p.SetValue(lhs, rhs);
                }
                catch
                {
                    relationships?[lhs, p] = oldRelation;

                    throw;
                }

                return true;
            }
            else
            {
                if (reportError)
                {
                    Error(manager, string.Format(SR.SerializerNoSuchProperty, lhs.GetType().FullName, propertyReferenceEx.PropertyName), SR.SerializerNoSuchProperty);
                }
            }
        }

        return false;
    }

    /// <summary>
    ///  This method returns an expression representing the given object. It may return null, indicating that
    ///  no expression has been set that describes the object. Expressions are acquired in one of three ways:
    ///  1. The expression could be the result of a prior SetExpression call.
    ///  2. The expression could have been found in the RootContext.
    ///  3. The expression could be derived through IReferenceService.
    ///  4. The current expression on the context stack has a PresetValue == value.
    ///  To derive expressions through IReferenceService, GetExpression asks the reference service if there
    ///  is a name for the given object. If the expression service returns a valid name, it checks to see if
    ///  there is a '.' in the name. This indicates that the expression service found this object as the return
    ///  value of a read only property on another object. If there is a '.', GetExpression will split the reference
    ///  into sub-parts. The leftmost part is a name that will be evaluated via manager.GetInstance. For each
    ///  subsequent part, a property reference expression will be built. The final expression will then be returned.
    ///  If the object did not have an expression set, or the object was not found in the reference service, null will
    ///  be returned from GetExpression, indicating there is no existing expression for the object.
    /// </summary>
    protected CodeExpression? GetExpression(IDesignerSerializationManager manager, object value)
    {
        CodeExpression? expression = null;

        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);

        // Is the expression part of a prior SetExpression call?

        if (manager.TryGetContext(out ExpressionTable? table))
        {
            expression = table.GetExpression(value);
        }

        // Check to see if this object represents the root context.
        if (expression is null)
        {
            if (manager.TryGetContext(out RootContext? rootEx) && ReferenceEquals(rootEx.Value, value))
            {
                expression = rootEx.Expression;
            }
        }

        // Now check IReferenceService.
        if (expression is null)
        {
            // perf: first try to retrieve objectName from DesignerSerializationManager
            // only then involve reference service if needed
            // this is done to avoid unnecessary ensuring\creating references

            string? objectName = manager.GetName(value);
            if (objectName is null || objectName.Contains('.'))
            {
                if (manager.GetService<IReferenceService>() is { } refSvc)
                {
                    objectName = refSvc.GetName(value);
                    if (objectName is not null && objectName.Contains('.'))
                    {
                        // This object name is built from sub objects. Assemble the graph of sub objects.
                        string[] nameParts = objectName.Split('.');

                        Debug.Assert(nameParts.Length > 0, "How can we fail to split when IndexOf succeeded?");

                        object? baseInstance = manager.GetInstance(nameParts[0]);

                        if (baseInstance is not null)
                        {
                            CodeExpression? baseExpression = SerializeToExpression(manager, baseInstance);

                            if (baseExpression is not null)
                            {
                                for (int idx = 1; idx < nameParts.Length; idx++)
                                {
                                    baseExpression = new CodePropertyReferenceExpression(baseExpression, nameParts[idx]);
                                }

                                expression = baseExpression;
                            }
                        }
                    }
                }
            }
        }

        // Finally, the expression context.
        if (expression is null)
        {
            if (manager.TryGetContext(out ExpressionContext? ctx) && ReferenceEquals(ctx.PresetValue, value))
            {
                expression = ctx.Expression;
            }
        }

        if (expression is not null)
        {
            // set up cache dependencies
            // we check to see if there is anything on the stack
            // if there is we make the parent entry a dependency of the current entry
            ComponentCache.Entry? parentEntry = manager.GetContext<ComponentCache.Entry>();

            object? parentEntryComponent = parentEntry?.Component;
            if (manager.TryGetContext(out ComponentCache? cache) && parentEntryComponent is not null &&
                parentEntryComponent != value /* don't make ourselves dependent with ourselves */)
            {
                ComponentCache.Entry? entry = cache.GetEntryAll(value);
                entry?.AddDependency(parentEntryComponent);
            }
        }

        return expression;
    }

    /// <summary>
    ///  Returns the serializer for the given value. This is cognizant that instance
    ///  attributes may be different from type attributes and will use a custom serializer
    ///  on the instance if it is present. If not, it will delegate to the serialization
    ///  manager.
    /// </summary>
    protected CodeDomSerializer? GetSerializer(IDesignerSerializationManager manager, object? value)
    {
        ArgumentNullException.ThrowIfNull(manager);

        if (value is not null)
        {
            AttributeCollection valueAttributes = GetAttributesHelper(manager, value);
            AttributeCollection typeAttributes = GetAttributesFromTypeHelper(manager, value.GetType());

            if (valueAttributes.Count != typeAttributes.Count)
            {
                // Ok, someone has stuffed custom attributes on this instance.
                // Since the serialization manager only takes types,
                // we've got to see if one of these custom attributes is a designer serializer attribute.
                string? valueSerializerTypeName = null;
                Type desiredSerializerType = typeof(CodeDomSerializer);
                DesignerSerializationManager? vsManager = manager as DesignerSerializationManager;
                foreach (Attribute a in valueAttributes)
                {
                    if (a is DesignerSerializerAttribute da)
                    {
                        Type? realSerializerType = vsManager is not null
                            ? vsManager.GetRuntimeType(da.SerializerBaseTypeName)
                            : manager.GetType(da.SerializerBaseTypeName!);

                        if (realSerializerType == desiredSerializerType)
                        {
                            valueSerializerTypeName = da.SerializerTypeName;
                            break;
                        }
                    }
                }

                // If we got a value serializer, we've got to do the same thing here for the type serializer.
                // We only care if the two are different
                if (valueSerializerTypeName is not null)
                {
                    foreach (Attribute a in typeAttributes)
                    {
                        if (a is DesignerSerializerAttribute da)
                        {
                            Type? realSerializerType = vsManager is not null
                                ? vsManager.GetRuntimeType(da.SerializerBaseTypeName)
                                : manager.GetType(da.SerializerBaseTypeName!);

                            if (realSerializerType == desiredSerializerType)
                            {
                                // Ok, we found a serializer. If it matches the one we found for the value, then we can still use the default implementation.
                                if (valueSerializerTypeName.Equals(da.SerializerTypeName))
                                {
                                    valueSerializerTypeName = null;
                                }

                                break;
                            }
                        }
                    }
                }

                // Finally, if we got a value serializer, we need to create it and use it.
                if (valueSerializerTypeName is not null)
                {
                    Type? serializerType = vsManager is not null
                        ? vsManager.GetRuntimeType(valueSerializerTypeName)
                        : manager.GetType(valueSerializerTypeName);

                    if (serializerType is not null && desiredSerializerType.IsAssignableFrom(serializerType))
                    {
                        return (CodeDomSerializer?)Activator.CreateInstance(serializerType);
                    }
                }
            }
        }

        // for serializing null, we pass null to the serialization manager otherwise,
        // external IDesignerSerializationProviders wouldn't be given a chance to
        // serialize null their own special way.
        Type? t = value?.GetType();

        return manager.GetSerializer<CodeDomSerializer>(t);
    }

    /// <summary>
    ///  Returns the serializer for the given value. This is cognizant that instance
    ///  attributes may be different from type attributes and will use a custom serializer
    ///  on the instance if it is present. If not, it will delegate to the serialization
    ///  manager.
    /// </summary>
    protected CodeDomSerializer? GetSerializer(IDesignerSerializationManager manager, Type valueType)
    {
        return manager.GetSerializer<CodeDomSerializer>(valueType);
    }

    protected bool IsSerialized(IDesignerSerializationManager manager, object value)
    {
        return IsSerialized(manager, value, false);
    }

    /// <summary>
    ///  This method returns true if the given value has been serialized before. For an object to
    ///  be considered serialized either it or another serializer must have called SetExpression, creating
    ///  a relationship between that object and a referring expression.
    /// </summary>
    protected bool IsSerialized(IDesignerSerializationManager manager, object value, bool honorPreset)
    {
        bool hasExpression = false;
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);

        // Is the expression part of a prior SetExpression call?
        if (manager.TryGetContext(out ExpressionTable? table))
        {
            hasExpression = honorPreset ? table.ContainsPresetExpression(value) : table.GetExpression(value) is not null;
        }

        return hasExpression;
    }

    /// <summary>
    ///  This method can be used to serialize an expression that represents the creation of the given object.
    ///  It is aware of instance descriptors and will return true for isComplete if the entire configuration for the
    ///  instance could be achieved.
    /// </summary>
    protected CodeExpression? SerializeCreationExpression(IDesignerSerializationManager manager, object value, out bool isComplete)
    {
        isComplete = false;
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);

        TypeConverter converter = TypeDescriptor.GetConverter(value);

        // See if there is an ExpressionContext with a preset value we're interested in. If so, that will dictate our creation expression.
        if (manager.TryGetContext(out ExpressionContext? ctx) && ReferenceEquals(ctx.PresetValue, value))
        {
            CodeExpression expression = ctx.Expression;
            // Okay, we found a preset creation expression. We just need to find if it isComplete.
            if (converter.CanConvertTo(typeof(InstanceDescriptor)))
            {
                if (converter.ConvertTo(value, typeof(InstanceDescriptor)) is InstanceDescriptor descriptor && descriptor.MemberInfo is not null)
                {
                    isComplete = descriptor.IsComplete;
                }
            }

            return expression;
        }

        // See if there is an instance descriptor for this type.
        if (converter.CanConvertTo(typeof(InstanceDescriptor)))
        {
            if (converter.ConvertTo(value, typeof(InstanceDescriptor)) is InstanceDescriptor descriptor && descriptor.MemberInfo is not null)
            {
                isComplete = descriptor.IsComplete;
                return SerializeInstanceDescriptor(manager, value, descriptor);
            }
        }

        // see if this thing is serializable
#pragma warning disable SYSLIB0050 // Type or member is obsolete
        if (GetReflectionTypeHelper(manager, value).IsSerializable && (value as IComponent)?.Site is null)
        {
            CodeExpression? expression = SerializeToResourceExpression(manager, value);
            if (expression is not null)
            {
                isComplete = true;
                return expression;
            }
        }
#pragma warning restore SYSLIB0050

        // No instance descriptor. See if we can get to a public constructor that takes no arguments
        ConstructorInfo? ctor = GetReflectionTypeHelper(manager, value).GetConstructor([]);
        if (ctor is not null)
        {
            isComplete = false;
            return new CodeObjectCreateExpression(TypeDescriptor.GetClassName(value)!, []);
        }

        // Nothing worked.
        return null;
    }

    private CodeExpression? SerializeInstanceDescriptor(IDesignerSerializationManager manager, object value, InstanceDescriptor descriptor)
    {
        CodeExpression expression;

        // Serialize all of the arguments.
        CodeExpression[] arguments = new CodeExpression[descriptor.Arguments.Count];
        object?[] argumentValues = new object[arguments.Length];
        ParameterInfo[]? parameters = null;

        if (arguments.Length > 0)
        {
            descriptor.Arguments.CopyTo(argumentValues, 0);
            if (descriptor.MemberInfo is MethodBase mi)
            {
                parameters = mi.GetParameters();
            }
        }

        for (int i = 0; i < arguments.Length; i++)
        {
            Debug.Assert(argumentValues is not null && parameters is not null, "These should have been allocated when the argument array was created.");
            object? arg = argumentValues[i];
            CodeExpression? exp = null;
            ExpressionContext? newCtx = null;

            // If there is an ExpressionContext on the stack, we need to fix up its type to be the parameter type,
            // so the argument objects get serialized correctly.
            if (manager.TryGetContext(out ExpressionContext? ctx))
            {
                newCtx = new ExpressionContext(ctx.Expression, parameters[i].ParameterType, ctx.Owner);
                manager.Context.Push(newCtx);
            }

            try
            {
                exp = SerializeToExpression(manager, arg);
            }
            finally
            {
                if (newCtx is not null)
                {
                    Debug.Assert(manager.Context.Current == newCtx, "Context stack corrupted.");
                    manager.Context.Pop();
                }
            }

            if (exp is not null)
            {
                // Assign over. See if we need a cast first.
                if (arg is not null && !parameters[i].ParameterType.IsAssignableFrom(arg.GetType()))
                {
                    exp = new CodeCastExpression(parameters[i].ParameterType, exp);
                }

                arguments[i] = exp;
            }
            else
            {
                return null;
            }
        }

        Type expressionType = descriptor.MemberInfo!.DeclaringType!;
        CodeTypeReference typeRef = new(expressionType);

        if (descriptor.MemberInfo is ConstructorInfo)
        {
            expression = new CodeObjectCreateExpression(typeRef, arguments);
        }
        else if (descriptor.MemberInfo is MethodInfo methodInfo)
        {
            CodeTypeReferenceExpression typeRefExp = new(typeRef);
            CodeMethodReferenceExpression methodRef = new(typeRefExp, methodInfo.Name);
            expression = new CodeMethodInvokeExpression(methodRef, arguments);
            expressionType = methodInfo.ReturnType;
        }
        else if (descriptor.MemberInfo is PropertyInfo propertyInfo)
        {
            CodeTypeReferenceExpression typeRefExp = new(typeRef);
            CodePropertyReferenceExpression propertyRef = new(typeRefExp, propertyInfo.Name);
            Debug.Assert(arguments.Length == 0, "Property serialization does not support arguments");
            expression = propertyRef;
            expressionType = propertyInfo.PropertyType;
        }
        else if (descriptor.MemberInfo is FieldInfo fieldInfo)
        {
            Debug.Assert(arguments.Length == 0, "Field serialization does not support arguments");
            CodeTypeReferenceExpression typeRefExp = new(typeRef);
            expression = new CodeFieldReferenceExpression(typeRefExp, fieldInfo.Name);
            expressionType = fieldInfo.FieldType;
        }
        else
        {
            Debug.Fail($"Unrecognized reflection type in instance descriptor: {descriptor.MemberInfo.GetType().Name}");
            return null;
        }

        // Finally, check to see if our value is assignable from the expression type. If not, then supply a cast.
        // The value may be an internal or protected type; if it is, then walk up its hierarchy until we find one
        // that is public.
        Type targetType = value.GetType();
        while (!targetType.IsPublic)
        {
            targetType = targetType.BaseType!;
        }

        if (!targetType.IsAssignableFrom(expressionType))
        {
            expression = new CodeCastExpression(targetType, expression);
        }

        return expression;
    }

    /// <summary>
    ///  This method returns a unique name for the given object. It first calls GetName from the serialization
    ///  manager, and if this does not return a name if fabricates a name for the object. To fabricate a name
    ///  it uses the INameCreationService to create valid names. If the service is not available instead the
    ///  method will fabricate a name based on the short type name combined with an index number to make
    ///  it unique. The resulting name is associated with the serialization manager by calling SetName before
    ///  the new name is returned.
    /// </summary>
    protected string GetUniqueName(IDesignerSerializationManager manager, object value)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);

        string? name = manager.GetName(value);
        if (name is null)
        {
            string baseName;
            Type targetType = GetReflectionTypeHelper(manager, value);
            INameCreationService? ns = manager.GetService<INameCreationService>();
            if (ns is not null)
            {
                baseName = ns.CreateName(null, targetType);
            }
            else
            {
                baseName = targetType.Name.ToLower(CultureInfo.InvariantCulture);
            }

            int suffixIndex = 1;
            ComponentCache? cache = manager.GetContext<ComponentCache>();

            // Declare this name to the serializer. If there is already a name defined, keep trying.
            do
            {
                name = $"{baseName}{suffixIndex++}";
            }
            while (manager.GetInstance(name) is not null || (cache is not null && cache.ContainsLocalName(name)));

            manager.SetName(value, name);
            if (manager.TryGetContext(out ComponentCache.Entry? entry))
            {
                entry.AddLocalName(name);
            }
        }

        return name;
    }

    /// <summary>
    ///  This serializes a single event for the given object.
    /// </summary>
    protected void SerializeEvent(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, EventDescriptor descriptor)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(statements);
        ArgumentNullException.ThrowIfNull(value);
        ArgumentNullException.ThrowIfNull(descriptor);

        // Now look for a MemberCodeDomSerializer for the property. If we can't find one, then we can't serialize the property
        manager.Context.Push(statements);
        manager.Context.Push(descriptor);
        try
        {
            MemberCodeDomSerializer? memberSerializer = manager.GetSerializer<MemberCodeDomSerializer>(descriptor.GetType());

            if (memberSerializer is not null && memberSerializer.ShouldSerialize(manager, value, descriptor))
            {
                memberSerializer.Serialize(manager, value, descriptor, statements);
            }
        }
        finally
        {
            Debug.Assert(manager.Context.Current == descriptor, "Context stack corrupted.");
            manager.Context.Pop();
            manager.Context.Pop();
        }
    }

    /// <summary>
    ///  This serializes all events for the given object.
    /// </summary>
    protected void SerializeEvents(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, params Attribute[]? filter)
    {
        EventDescriptorCollection events = GetEventsHelper(manager, value, filter).Sort();
        foreach (EventDescriptor evt in events)
        {
            SerializeEvent(manager, statements, value, evt);
        }
    }

    /// <summary>
    ///  This serializes all properties for the given object, using the provided filter.
    /// </summary>
    protected void SerializeProperties(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, Attribute[]? filter)
    {
        PropertyDescriptorCollection properties = GetFilteredProperties(manager, value, filter).Sort();
        InheritanceAttribute? inheritance = (InheritanceAttribute?)GetAttributesHelper(manager, value)[typeof(InheritanceAttribute)];

        inheritance ??= InheritanceAttribute.NotInherited;

        manager.Context.Push(inheritance);
        try
        {
            foreach (PropertyDescriptor property in properties)
            {
                if (!property.Attributes.Contains(DesignerSerializationVisibilityAttribute.Hidden))
                {
                    SerializeProperty(manager, statements, value, property);
                }
            }
        }
        finally
        {
            Debug.Assert(manager.Context.Current == inheritance, "Somebody messed up our context stack.");
            manager.Context.Pop();
        }
    }

    private static PropertyDescriptorCollection GetFilteredProperties(IDesignerSerializationManager manager, object value, Attribute[]? filter)
    {
        PropertyDescriptorCollection props = GetPropertiesHelper(manager, value, filter);
        if (value is IComponent comp)
        {
            if (((IDictionary)props).IsReadOnly)
            {
                PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
                props.CopyTo(propArray, 0);
                props = new PropertyDescriptorCollection(propArray);
            }

            PropertyDescriptor? filterProp = manager.Properties["FilteredProperties"];
            if (filterProp?.GetValue(manager) is ITypeDescriptorFilterService filterSvc)
            {
                filterSvc.FilterProperties(comp, props);
            }
        }

        return props;
    }

    /// <summary>
    ///  This method will inspect all of the properties on the given object fitting the filter, and check for that
    ///  property in a resource blob. This is useful for deserializing properties that cannot be represented
    ///  in code, such as design-time properties.
    /// </summary>
    protected void SerializePropertiesToResources(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, Attribute[]? filter)
    {
        PropertyDescriptorCollection props = GetPropertiesHelper(manager, value, filter);
        manager.Context.Push(statements);
        try
        {
            CodeExpression? target = SerializeToExpression(manager, value);
            if (target is not null)
            {
                CodePropertyReferenceExpression propertyRef = new(target, string.Empty);
                foreach (PropertyDescriptor property in props)
                {
                    ExpressionContext tree = new(propertyRef, property.PropertyType, value);
                    manager.Context.Push(tree);
                    try
                    {
                        if (property.Attributes.Contains(DesignerSerializationVisibilityAttribute.Visible))
                        {
                            propertyRef.PropertyName = property.Name;

                            string? name;
                            if (target is CodeThisReferenceExpression)
                            {
                                name = "$this";
                            }
                            else
                            {
                                name = manager.GetName(value);
                            }

                            name = $"{name}.{property.Name}";
                            ResourceCodeDomSerializer.SerializeMetadata(manager, name, property.GetValue(value), property.ShouldSerializeValue(value));
                        }
                    }
                    finally
                    {
                        Debug.Assert(manager.Context.Current == tree, "Context stack corrupted.");
                        manager.Context.Pop();
                    }
                }
            }
        }
        finally
        {
            Debug.Assert(manager.Context.Current == statements, "Context stack corrupted.");
            manager.Context.Pop();
        }
    }

    /// <summary>
    ///  This serializes the given property for the given object.
    /// </summary>
    protected void SerializeProperty(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, PropertyDescriptor propertyToSerialize)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);
        ArgumentNullException.ThrowIfNull(propertyToSerialize);
        ArgumentNullException.ThrowIfNull(statements);

        // Now look for a MemberCodeDomSerializer for the property. If we can't find one, then we can't serialize the property
        manager.Context.Push(statements);
        manager.Context.Push(propertyToSerialize);
        try
        {
            MemberCodeDomSerializer? memberSerializer = manager.GetSerializer<MemberCodeDomSerializer>(propertyToSerialize.GetType());
            if (memberSerializer is not null && memberSerializer.ShouldSerialize(manager, value, propertyToSerialize))
            {
                memberSerializer.Serialize(manager, value, propertyToSerialize, statements);
            }
        }
        finally
        {
            Debug.Assert(manager.Context.Current == propertyToSerialize, "Context stack corrupted.");
            manager.Context.Pop();
            manager.Context.Pop();
        }
    }

    /// <summary>
    ///  Writes the given resource value under the given name. The resource is written to the
    ///  current CultureInfo the user is using to design with.
    /// </summary>
    protected void SerializeResource(IDesignerSerializationManager manager, string resourceName, object? value)
    {
        ResourceCodeDomSerializer.WriteResource(manager, resourceName, value);
    }

    /// <summary>
    ///  Writes the given resource value under the given name. The resource is written to the
    ///  invariant culture.
    /// </summary>
    protected void SerializeResourceInvariant(IDesignerSerializationManager manager, string resourceName, object? value)
    {
        ResourceCodeDomSerializer.WriteResourceInvariant(manager, resourceName, value);
    }

    /// <summary>
    ///  This is a helper method that serializes a value to an expression. It will return a CodeExpression if the
    ///  value can be serialized, or null if it can't. SerializeToExpression uses the following rules for serializing
    ///  types:
    ///  1. It first calls GetExpression to see if an expression has already been created for the object. If so, it
    ///  returns the existing expression.
    ///  2. It then locates the object's serializer, and asks it to serialize.
    ///  3. If the return value of the object's serializer is a CodeExpression, the expression is returned.
    ///  4. It finally makes one last call to GetExpression to see if the serializer added an expression.
    ///  5. Finally, it returns null.
    ///  If no expression could be created and no suitable serializer could be found, an error will be
    ///  reported through the serialization manager. No error will be reported if a serializer was found
    ///  but it failed to produce an expression. It is assumed that the serializer either already reported
    ///  the error, or does not wish to serialize the object.
    /// </summary>
    protected CodeExpression? SerializeToExpression(IDesignerSerializationManager manager, object? value)
    {
        CodeExpression? expression = null;

        // We do several things here:
        // First, we check to see if there is already an expression for this object by
        //     calling IsSerialized / GetExpression.
        // Failing that we check GetLegacyExpression to see if we are working with an old serializer.
        // Failing that, we invoke the object's serializer.
        //     If that serializer returned a CodeExpression, we will use it.
        //     If the serializer did not return a code expression, we call GetExpression one last time to see
        //     if the serializer added an expression. If it did, we use it. Otherwise we return null.
        // If the serializer was invoked and it created one or more statements those statements
        //     will be added to a statement collection.
        // Additionally, if there is a statement context that contains a statement table for this object we
        //     will push that statement table onto the context stack in case someone else needs statements.
        if (value is not null)
        {
            if (IsSerialized(manager, value))
            {
                expression = GetExpression(manager, value);
            }
            else
            {
                expression = GetLegacyExpression(manager, value);
                if (expression is not null)
                {
                    SetExpression(manager, value, expression);
                }
            }
        }

        if (expression is null)
        {
            CodeDomSerializer? serializer = GetSerializer(manager, value);
            if (serializer is not null)
            {
                CodeStatementCollection? saveStatements = null;
                if (value is not null)
                {
                    // The Whidbey model for serializing a complex object is to call SetExpression with the object's reference
                    // expression and then call on the various Serialize Property / Event methods. This is incompatible with
                    // legacy code, and if not handled legacy code may serialize incorrectly or even stack fault. To handle
                    // this, we keep a private "Legacy Expression Table". This is a table that we fill in here. We don't fill
                    // in the actual legacy expression here. Rather, we fill it with a marker value and obtain the legacy
                    // expression above in GetLegacyExpression. If we hit this case, we then save the expression in GetExpression
                    // so that future calls to IsSerialized will succeed.
                    SetLegacyExpression(manager, value);
                    if (manager.TryGetContext(out StatementContext? statementCtx))
                    {
                        saveStatements = statementCtx.StatementCollection[value];
                    }

                    if (saveStatements is not null)
                    {
                        manager.Context.Push(saveStatements);
                    }
                }

                object? result = null;
                try
                {
                    result = serializer.Serialize(manager, value!);
                }
                finally
                {
                    if (saveStatements is not null)
                    {
                        Debug.Assert(manager.Context.Current == saveStatements, "Context stack corrupted.");
                        manager.Context.Pop();
                    }
                }

                expression = result as CodeExpression;
                if (expression is null && value is not null)
                {
                    expression = GetExpression(manager, value);
                }

                // If the result is a statement or a group of statements,
                // we need to see if there is a code statement collection on the stack we can push the statements into.
                CodeStatementCollection? statements = result as CodeStatementCollection;
                if (statements is null)
                {
                    if (result is CodeStatement statement)
                    {
                        statements = new CodeStatementCollection
                        {
                            statement
                        };
                    }
                }

                if (statements is not null)
                {
                    // See if we have a place for these statements to be stored. If not, then check the context.
                    saveStatements ??= manager.GetContext<CodeStatementCollection>();

                    if (saveStatements is not null)
                    {
                        Debug.Assert(saveStatements != statements, "The serializer returned the same collection that exists on the context stack.");
                        saveStatements.AddRange(statements);
                    }
                    else
                    {
                        // If we got here we will be losing data because we have no avenue to save these statements. Inform the user.
                        string valueName = "(null)";
                        if (value is not null)
                        {
                            valueName = manager.GetName(value) ?? value.GetType().Name;
                        }

                        manager.ReportError(string.Format(SR.SerializerLostStatements, valueName));
                    }
                }
            }
            else
            {
                manager.ReportError(string.Format(SR.SerializerNoSerializerForComponent, value is null ? "(null)" : value.GetType().FullName));
            }
        }

        return expression;
    }

    private static CodeExpression? GetLegacyExpression(IDesignerSerializationManager manager, object value)
    {
        CodeExpression? expression = null;
        if (manager.TryGetContext(out LegacyExpressionTable? table))
        {
            object? exp = table[value];
            if (exp == value)
            {
                // Sentinel. Compute an actual legacy expression to store.
                string? name = manager.GetName(value);
                bool referenceName = false;
                if (name is null)
                {
                    name = manager.GetService<IReferenceService>()?.GetName(value);
                    referenceName = name is not null;
                }

                if (name is not null)
                {
                    // Check to see if this is a reference to the root component. If it is, then use "this".
                    if (manager.TryGetContext(out RootContext? root))
                    {
                        if (root.Value == value)
                        {
                            expression = root.Expression;
                        }
                        else
                        {
                            int dotIndex = name.IndexOf('.');

                            if (referenceName && dotIndex >= 0)
                            {
                                // if it's a reference name with a dot, we've actually got a property here...
                                expression = new CodePropertyReferenceExpression(new CodeFieldReferenceExpression(root.Expression, name[..dotIndex]), name[(dotIndex + 1)..]);
                            }
                            else
                            {
                                expression = new CodeFieldReferenceExpression(root.Expression, name);
                            }
                        }
                    }
                    else
                    {
                        int dotIndex = name.IndexOf('.');

                        // A variable reference
                        if (referenceName && dotIndex >= 0)
                        {
                            // if it's a reference name with a dot, we've actually got a property here...
                            expression = new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(name[..dotIndex]), name[(dotIndex + 1)..]);
                        }
                        else
                        {
                            expression = new CodeVariableReferenceExpression(name);
                        }
                    }
                }

                table[value] = expression;
            }
            else
            {
                expression = exp as CodeExpression;
            }
        }

        return expression;
    }

    private static void SetLegacyExpression(IDesignerSerializationManager manager, object value)
    {
        if (value is IComponent)
        {
            if (!manager.TryGetContext(out LegacyExpressionTable? table))
            {
                table = new LegacyExpressionTable();
                manager.Context.Append(table);
            }

            table[value] = value;
        }
    }

    private class LegacyExpressionTable : Hashtable
    {
    }

    /// <summary>
    ///  Serializes the given object to a resource and returns a code expression that represents the resource.
    ///  This will return null if the value cannot be serialized. If ensureInvariant is true, this will ensure that
    ///  new values make their way into the invariant culture. Normally, this is desirable. Otherwise a resource
    ///  GetValue call could fail if reading from a culture that doesn't have a value. You should only pass
    ///  false to ensureInvariant when you intend to read resources differently than directly asking for a value.
    ///  The default value of insureInvariant is true.
    /// </summary>
    protected CodeExpression? SerializeToResourceExpression(IDesignerSerializationManager manager, object? value)
    {
        return SerializeToResourceExpression(manager, value, true);
    }

    /// <summary>
    ///  Serializes the given object to a resource and returns a code expression that represents the resource.
    ///  This will return null if the value cannot be serialized. If ensureInvariant is true, this will ensure that
    ///  new values make their way into the invariant culture. Normally, this is desirable. Otherwise a resource
    ///  GetValue call could fail if reading from a culture that doesn't have a value. You should only pass
    ///  false to ensureInvariant when you intend to read resources differently than directly asking for a value.
    ///  The default value of insureInvariant is true.
    /// </summary>
    protected CodeExpression? SerializeToResourceExpression(IDesignerSerializationManager manager, object? value, bool ensureInvariant)
    {
        CodeExpression? result = null;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
        if (value is null || value.GetType().IsSerializable)
        {
            CodeStatementCollection? saveStatements = null;
            if (value is not null)
            {
                if (manager.TryGetContext(out StatementContext? statementCtx))
                {
                    saveStatements = statementCtx.StatementCollection[value];
                }

                if (saveStatements is not null)
                {
                    manager.Context.Push(saveStatements);
                }
            }

            try
            {
                result = ResourceCodeDomSerializer.Default.Serialize(manager, value, false, ensureInvariant) as CodeExpression;
            }
            finally
            {
                if (saveStatements is not null)
                {
                    Debug.Assert(manager.Context.Current == saveStatements, "Context stack corrupted.");
                    manager.Context.Pop();
                }
            }
        }
#pragma warning restore SYSLIB0050

        return result;
    }

    protected void SetExpression(IDesignerSerializationManager manager, object value, CodeExpression expression)
    {
        SetExpression(manager, value, expression, false);
    }

    /// <summary>
    ///  This is a helper method that associates a CodeExpression with an object. Objects that have been associated
    ///  with expressions in this way are accessible through the GetExpression method. SetExpression stores its
    ///  expression table as an appended object on the context stack so it is accessible by any serializer's
    ///  GetExpression method.
    /// </summary>
    protected void SetExpression(IDesignerSerializationManager manager, object value, CodeExpression expression, bool isPreset)
    {
        ArgumentNullException.ThrowIfNull(manager);
        ArgumentNullException.ThrowIfNull(value);
        ArgumentNullException.ThrowIfNull(expression);

        if (!manager.TryGetContext(out ExpressionTable? table))
        {
            table = new ExpressionTable();
            manager.Context.Append(table);
        }

        table.SetExpression(value, expression, isPreset);
    }

    internal static void FillStatementTable(IDesignerSerializationManager manager, IDictionary table, CodeStatementCollection statements)
    {
        FillStatementTable(manager, table, null, statements, null);
    }

    internal static void FillStatementTable(IDesignerSerializationManager manager, IDictionary table, Dictionary<string, string>? names, CodeStatementCollection statements, string? className)
    {
        // Look in the method body to try to find statements with a LHS that points to a name in our nametable.
        foreach (CodeStatement statement in statements)
        {
            CodeExpression? expression = null;
            if (statement is CodeAssignStatement assign)
            {
                expression = assign.Left;
            }
            else if (statement is CodeAttachEventStatement attachEvent)
            {
                expression = attachEvent.Event;
            }
            else if (statement is CodeRemoveEventStatement removeEvent)
            {
                expression = removeEvent.Event;
            }
            else if (statement is CodeExpressionStatement expressionStmt)
            {
                expression = expressionStmt.Expression;
            }
            else if (statement is CodeVariableDeclarationStatement variableDecl)
            {
                AddStatement(table, variableDecl.Name, variableDecl);

                if (names is not null && variableDecl.Type is not null && !string.IsNullOrEmpty(variableDecl.Type.BaseType))
                {
                    names[variableDecl.Name] = GetTypeNameFromCodeTypeReference(manager, variableDecl.Type);
                }

                expression = null;
            }

            if (expression is not null)
            {
                // Simplify the expression as much as we can, looking for our target object in the process. If we find an
                // expression that refers to our target object, we're done and can move on to the next statement.
                while (true)
                {
                    if (expression is CodeCastExpression castEx)
                    {
                        expression = castEx.Expression;
                    }
                    else if (expression is CodeDelegateCreateExpression delegateCreateEx)
                    {
                        expression = delegateCreateEx.TargetObject;
                    }
                    else if (expression is CodeDelegateInvokeExpression delegateInvokeEx)
                    {
                        expression = delegateInvokeEx.TargetObject;
                    }
                    else if (expression is CodeDirectionExpression directionEx)
                    {
                        expression = directionEx.Expression;
                    }
                    else if (expression is CodeEventReferenceExpression eventReferenceEx)
                    {
                        expression = eventReferenceEx.TargetObject;
                    }
                    else if (expression is CodeMethodInvokeExpression methodInvokeEx)
                    {
                        expression = methodInvokeEx.Method;
                    }
                    else if (expression is CodeMethodReferenceExpression methodReferenceEx)
                    {
                        expression = methodReferenceEx.TargetObject;
                    }
                    else if (expression is CodeArrayIndexerExpression arrayIndexerEx)
                    {
                        expression = arrayIndexerEx.TargetObject;
                    }
                    else if (expression is CodeFieldReferenceExpression fieldReferenceEx)
                    {
                        // For fields we need to check to see if the field name is equal to the target object.
                        // If it is, then we have the expression we want. We can add the statement here
                        // and then break out of our loop.
                        // Note:  We cannot validate that this is a name in our nametable.
                        // The nametable only contains names we have discovered through
                        // code parsing and will not include data from any inherited objects.
                        // We accept the field now, and then fail later if we try to resolve
                        // it to an object and we can't find it.
                        bool addedStatement = false;
                        if (fieldReferenceEx.TargetObject is CodeThisReferenceExpression)
                        {
                            Type? type = GetType(manager, fieldReferenceEx.FieldName, names);
                            if (type is not null)
                            {
                                if (manager.TryGetSerializer(type, out CodeDomSerializer? serializer))
                                {
                                    string? componentName = serializer.GetTargetComponentName(statement, expression, type);
                                    if (!string.IsNullOrEmpty(componentName))
                                    {
                                        AddStatement(table, componentName, statement);
                                        addedStatement = true;
                                    }
                                }
                            }

                            if (!addedStatement)
                            {
                                // we still want to do this in case of the "Note" above.
                                AddStatement(table, fieldReferenceEx.FieldName, statement);
                            }

                            break;
                        }
                        else
                        {
                            expression = fieldReferenceEx.TargetObject;
                        }
                    }
                    else if (expression is CodePropertyReferenceExpression propertyReferenceEx)
                    {
                        // For properties we need to check to see if the property name is equal to the target object.
                        // If it is, then we have the expression we want. We can add the statement here and
                        // then break out of our loop.
                        if (propertyReferenceEx.TargetObject is CodeThisReferenceExpression && (names is null || names.ContainsKey(propertyReferenceEx.PropertyName)))
                        {
                            AddStatement(table, propertyReferenceEx.PropertyName, statement);
                            break;
                        }
                        else
                        {
                            expression = propertyReferenceEx.TargetObject;
                        }
                    }
                    else if (expression is CodeVariableReferenceExpression variableReferenceEx)
                    {
                        // For variables we need to check to see if the variable name is equal to the target object.
                        // If it is, then we have the expression we want. We can add the statement here and
                        // then break out of our loop.
                        bool statementAdded = false;
                        if (names is not null)
                        {
                            Type? type = GetType(manager, variableReferenceEx.VariableName, names);
                            if (type is not null)
                            {
                                if (manager.TryGetSerializer(type, out CodeDomSerializer? serializer))
                                {
                                    string? componentName = serializer.GetTargetComponentName(statement, expression, type);
                                    if (!string.IsNullOrEmpty(componentName))
                                    {
                                        AddStatement(table, componentName, statement);
                                        statementAdded = true;
                                    }
                                }
                            }
                        }
                        else
                        {
                            AddStatement(table, variableReferenceEx.VariableName, statement);
                            statementAdded = true;
                        }

                        if (!statementAdded)
                        {
                            manager.ReportError(new CodeDomSerializerException(string.Format(SR.SerializerUndeclaredName, variableReferenceEx.VariableName), manager));
                        }

                        break;
                    }
                    else if (expression is CodeThisReferenceExpression or CodeBaseReferenceExpression)
                    {
                        // We cannot go any further than "this". So, we break out of the loop. We file this statement under the root object.
                        Debug.Assert(className is not null, "FillStatementTable expected a valid className but received null");
                        if (className is not null)
                        {
                            AddStatement(table, className, statement);
                        }

                        break;
                    }
                    else
                    {
                        // We cannot simplify this expression any further, so we stop looping.
                        break;
                    }
                }
            }
        }
    }

    internal static Type? GetType(IDesignerSerializationManager manager, string name, Dictionary<string, string>? names)
    {
        Type? type = null;
        if (names is not null && names.TryGetValue(name, out string? typeName))
        {
            if (manager is not null && !string.IsNullOrEmpty(typeName))
            {
                type = manager.GetType(typeName);
            }
        }

        return type;
    }

    private static void AddStatement(IDictionary table, string name, CodeStatement statement)
    {
        OrderedCodeStatementCollection? statements;
        if (table.Contains(name))
        {
            statements = (OrderedCodeStatementCollection)table[name]!;
        }
        else
        {
            // push in an order key so we know what position this item was in the list of declarations. this allows us to preserve ZOrder.
            statements = new OrderedCodeStatementCollection(table.Count, name);
            table[name] = statements;
        }

        statements.Add(statement);
    }

    internal class OrderedCodeStatementCollection : CodeStatementCollection
    {
        public readonly int Order;
        public readonly string Name;

        public OrderedCodeStatementCollection(int order, string name)
        {
            Order = order;
            Name = name;
        }
    }
}
